﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Grimgine.Math
{
    /// <summary>
    /// A custom random class that generates random numbers from
    /// a uniform distribution using the Mersenne Twister algorithm.
    /// </summary>
    class Random
    {
        // Period parameters
        private const int N = 624;
        private const int M = 397;
        private const uint MATRIX_A = 0x9908b0dfU;  // constant vector a
        private const uint UPPER_MASK = 0x80000000; // most significant w-r bits
        private const uint LOWER_MASK = 0x7fffffff; // least significant r bits

        // Tempering parameters
        private const uint TEMPERING_MASK_B = 0x9d2c5680U;
        private const uint TEMPERING_MASK_C = 0xefc60000U;

        private uint rseed;
        private uint rseed_sp;
        private uint[] mt = new uint[N];    // array for the state vector
        private int mti; // mti == N+1 means mt[N] is not initialised

        private uint[] mag01 = { 0x0U, MATRIX_A };

        public uint Seed
        {
            get { return rseed; }
        }

        public Random()
        {
            rseed = 1;
            // safe0 start
            rseed_sp = 0;
            mti = N + 1;
            // safe0 end
        }

        /// <summary>
        /// Generates a random number in the range [0, 0xffffffff]
        /// </summary>
        public uint Next()
        {
            uint y;

            // mag01[x] = x * MATRIX_A for x=0,1

            // Generate N words at one time
            if (mti >= N)
            {
                int kk;
                // if sgenrand() has not been called.
                if (mti == N + 1)
                    SetRandomSeed(4357); // a default initial seed is used

                for (kk = 0; kk < N - M; kk++)
                {
                    y = (mt[kk] & UPPER_MASK) | (mt[kk + 1] & LOWER_MASK);
                    mt[kk] = mt[kk + M] ^ (y >> 1) ^ mag01[y & 0x1];
                }
                for (; kk < N - 1; kk++)
                {
                    y = (mt[kk] & UPPER_MASK) | (mt[kk + 1] & LOWER_MASK);
                    mt[kk] = mt[kk + (M - N)] ^ (y >> 1) ^ mag01[y & 0x1];
                }

                y = (mt[N - 1] & UPPER_MASK) | (mt[0] & LOWER_MASK);
                mt[N - 1] = mt[M - 1] ^ (y >> 1) ^ mag01[y & 0x1];

                mti = 0;
            }

            y = mt[mti++];
            y ^= (y >> 11);
            y ^= (y << 7) & 0x9d2c5680U;
            y ^= (y << 15) & 0xefc60000U;
            y ^= (y >> 18);

            return y;
        }

        /// <summary>
        /// Returns a random number with in the range [0, n]
        /// <strong>n has to be greater than 0</strong>
        /// </summary>
        /// <param name="n">The upper bound of the range</param>
        /// <returns>A random number greater than or equal to <c>0</c> and less than or equal to <c>n</c></returns>
        public uint Next(uint n)
        {
            uint y;
            if (n == 0)
                return 0;

            // mag01[x] = x * MATRIX_A for x=0,1

            // Generate N words at one time
            if (mti >= N)
            {
                int kk;
                // if sgenrand() has not been called.
                if (mti == N + 1)
                    SetRandomSeed(4357); // a default initial seed is used

                for (kk = 0; kk < N - M; kk++)
                {
                    y = (mt[kk] & UPPER_MASK) | (mt[kk + 1] & LOWER_MASK);
                    mt[kk] = mt[kk + M] ^ (y >> 1) ^ mag01[y & 0x1];
                }
                for (; kk < N - 1; kk++)
                {
                    y = (mt[kk] & UPPER_MASK) | (mt[kk + 1] & LOWER_MASK);
                    mt[kk] = mt[kk + (M - N)] ^ (y >> 1) ^ mag01[y & 0x1];
                }

                y = (mt[N - 1] & UPPER_MASK) | (mt[0] & LOWER_MASK);
                mt[N - 1] = mt[M - 1] ^ (y >> 1) ^ mag01[y & 0x1];

                mti = 0;
            }

            y = mt[mti++];
            y ^= (y >> 11);
            y ^= (y << 7) & 0x9d2c5680U;
            y ^= (y << 15) & 0xefc60000U;
            y ^= (y >> 18);

            return (y % n);
        }

        public float Next()
        {
            float r = (float)Next(uint.MaxValue);
            float divisor = (float)uint.MaxValue;
            return (r / divisor);
        }

        public void SetRandomSeed(uint n)
        {
            // setting initial seeds to mt[N] using
            // the generator Line 25 of Table 1 in
            // [Knuth 1981, HTe Art of Computer Programming
            //      Vol. 2 (2nd Ed.), pp102]
            mt[0] = n & 0xffffffff;
            for (mti = 1; mti < N; mti++)
                mt[mti] = (69069 * mt[mti - 1]) & 0xffffffff;

            rseed = n;
        }

        public void Randomise()
        {
            SetRandomSeed((uint)DateTime.Now.Millisecond);
        }
    }
}
